001 /*
002 * Copyright 2005 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.depot;
020
021 import java.io.File;
022 import java.net.URL;
023 import java.net.URI;
024 import java.rmi.RMISecurityManager;
025 import java.util.ArrayList;
026 import java.util.Date;
027
028 import net.dpml.transit.Disposable;
029 import net.dpml.transit.Transit;
030 import net.dpml.transit.TransitError;
031 import net.dpml.transit.DefaultTransitModel;
032 import net.dpml.transit.model.TransitModel;
033 import net.dpml.transit.monitor.Adapter;
034 import net.dpml.transit.monitor.LoggingAdapter;
035 import net.dpml.transit.monitor.RepositoryMonitorAdapter;
036 import net.dpml.transit.monitor.CacheMonitorAdapter;
037 import net.dpml.transit.monitor.NetworkMonitorAdapter;
038
039 import net.dpml.lang.Enum;
040 import net.dpml.lang.PID;
041 import net.dpml.lang.Part;
042
043 import net.dpml.util.Logger;
044 import net.dpml.util.PropertyResolver;
045
046 /**
047 * CLI hander for the depot package.
048 *
049 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
050 * @version 1.0.2
051 */
052 public final class Main //implements ShutdownHandler
053 {
054 private static Main m_MAIN;
055 private static final PID PROCESS_ID = new PID();
056
057 private Object m_plugin;
058 private boolean m_debug = false;
059 private boolean m_trace = false;
060
061 /**
062 * Processes command line options to establish the command handler plugin to deploy.
063 * Command parameters recognixed by the console include the following:
064 * <ul>
065 * <li>-Ddpml.depot.addplication=transit|station|metro|build</li>
066 * <li>-debug</li>
067 * </ul>
068 * @param args the command line argument array
069 * @exception Exception if a error occurs
070 */
071 public static void main( String[] args )
072 throws Exception
073 {
074 if( null != m_MAIN )
075 {
076 final String error =
077 "Console already established.";
078 throw new IllegalArgumentException( error );
079 }
080 else
081 {
082 m_MAIN = new Main( args );
083 }
084 }
085
086 private Main( String[] arguments )
087 {
088 String[] args = processSystemProperties( arguments );
089
090 //
091 // check for debug and trace cli options
092 //
093
094 if( CLIHelper.isOptionPresent( args, "-trace" ) )
095 {
096 args = CLIHelper.consolidate( args, "-trace" );
097 System.setProperty( "dpml.trace", "true" );
098 m_trace = true;
099 }
100
101 if( CLIHelper.isOptionPresent( args, "-debug" ) )
102 {
103 args = CLIHelper.consolidate( args, "-debug" );
104 System.setProperty( "dpml.debug", "true" );
105 m_debug = true;
106 }
107
108 //
109 // handle cli sub-system establishment
110 //
111
112 Command command = getCommand( args );
113 if( Command.STATION.equals( command ) )
114 {
115 handleStation( args );
116 }
117 else
118 {
119 if( null == System.getProperty( "dpml.logging.config" ) )
120 {
121 if( m_trace )
122 {
123 System.setProperty( "dpml.logging.config", "local:properties:dpml/transit/trace" );
124 }
125 else if( m_debug )
126 {
127 System.setProperty( "dpml.logging.config", "local:properties:dpml/transit/debug" );
128 }
129 else
130 {
131 System.setProperty( "dpml.logging.config", "local:properties:dpml/transit/default" );
132 }
133 }
134
135 if( m_debug || m_trace )
136 {
137 for( int i=0; i<arguments.length; i++ )
138 {
139 getLogger().debug( "arg[" + i + "]: " + arguments[i] );
140 }
141 }
142
143 if( Command.BUILD.equals( command ) )
144 {
145 handleBuild( args );
146 }
147 else if( Command.TRANSIT.equals( command ) )
148 {
149 handleTransit( args );
150 }
151 else if( Command.METRO.equals( command ) )
152 {
153 handleMetro( args );
154 }
155 else
156 {
157 final String error =
158 "Missing application key '" + APPLICATION_KEY + "'.";
159 System.err.println( error );
160 System.exit( 1 );
161 }
162 }
163 }
164
165 private void handleBuild( String[] args )
166 {
167 if( getLogger().isTraceEnabled() )
168 {
169 getLogger().trace( "launching builder" );
170 }
171 String name = "build";
172 String spec = "link:part:dpml/depot/dpml-library-build";
173 handlePlugin( name, spec, args, false );
174 }
175
176 private void handleMetro( String[] args )
177 {
178 if( getLogger().isTraceEnabled() )
179 {
180 getLogger().trace( "launching metro" );
181 }
182 String name = "exec";
183 String spec = "link:part:dpml/station/dpml-station-exec";
184 handlePlugin( name, spec, args, true );
185 }
186
187 private void handleTransit( String[] args )
188 {
189 String name = "transit";
190 String spec = "link:part:dpml/transit/dpml-transit-console";
191 handlePlugin( name, spec, args, false );
192 }
193
194 private void handleStation( String[] args )
195 {
196 new File( Transit.DPML_DATA, "logs/station" ).mkdirs();
197 if( CLIHelper.isOptionPresent( args, "-server" ) )
198 {
199 if( m_trace )
200 {
201 System.setProperty( "dpml.logging.level", "FINEST" );
202 }
203 else if( m_debug )
204 {
205 System.setProperty( "dpml.logging.level", "FINE" );
206 }
207 if( getLogger().isTraceEnabled() )
208 {
209 getLogger().trace( "launching station in server mode" );
210 }
211 String name = "station";
212 args = CLIHelper.consolidate( args, "-server" );
213 String spec = "link:part:dpml/station/dpml-station-server";
214 handlePlugin( name, spec, args, true );
215 }
216 else
217 {
218 if( getLogger().isTraceEnabled() )
219 {
220 getLogger().trace( "launching station in client mode" );
221 }
222 String name = "station";
223 String spec = "link:part:dpml/station/dpml-station-console";
224 handlePlugin( name, spec, args, false );
225 }
226 }
227
228 private void handlePlugin( String name, String spec, String[] args, boolean wait )
229 {
230 System.setSecurityManager( new RMISecurityManager() );
231 TransitModel model = getTransitModel( args );
232 boolean waitForCompletion = deployHandler( model, name, spec, args, wait );
233 if( !waitForCompletion )
234 {
235 if( m_plugin instanceof Disposable )
236 {
237 Disposable disposable = (Disposable) m_plugin;
238 disposable.dispose();
239 }
240 if( model instanceof DefaultTransitModel )
241 {
242 DefaultTransitModel disposable = (DefaultTransitModel) model;
243 disposable.dispose();
244 }
245 System.exit( 0 );
246 }
247 }
248
249 private boolean deployHandler(
250 TransitModel model, String command, String path, String[] args, boolean waitFor )
251 {
252 Logger logger = getLogger();
253 if( logger.isDebugEnabled() )
254 {
255 logger.debug( "date: " + new Date() );
256 logger.debug( "system: " + command );
257 logger.debug( "uri: " + path );
258 logger.debug( "args: [" + toString( args ) + "]" );
259 logger.debug( "system classloader: ["
260 + System.identityHashCode( ClassLoader.getSystemClassLoader() )
261 + "]" );
262 }
263 Logger log = resolveLogger( logger, command );
264 try
265 {
266 URI uri = new URI( path );
267 Transit transit = Transit.getInstance( model );
268 setupMonitors( transit, (Adapter) logger );
269
270 Part part = Part.load( uri, true );
271 m_plugin =
272 part.instantiate(
273 new Object[]
274 {
275 model,
276 args,
277 log
278 }
279 );
280 }
281 catch( GeneralException e )
282 {
283 getLogger().error( e.getMessage() );
284 System.exit( 1 );
285 }
286 catch( Exception e )
287 {
288 Throwable cause = e.getCause();
289 if( ( null != cause ) && ( cause instanceof GeneralException ) )
290 {
291 getLogger().error( cause.getMessage() );
292 System.exit( 1 );
293 }
294 else
295 {
296 getLogger().error( e.getMessage(), e.getCause() );
297 System.exit( 1 );
298 }
299 }
300 catch( Throwable e )
301 {
302 final String error =
303 "Deloyment failure."
304 + "\nTarget: " + command
305 + "\n URI: " + path;
306 getLogger().error( error, e );
307 System.exit( 1 );
308 }
309
310 if( m_plugin instanceof Runnable )
311 {
312 getLogger().debug( "starting " + m_plugin.getClass().getName() );
313 Thread thread = new Thread( (Runnable) m_plugin );
314 thread.start();
315 setShutdownHook( thread );
316 return true;
317 }
318 else
319 {
320 getLogger().debug( "deployed " + m_plugin.getClass().getName() );
321 return waitFor;
322 }
323 }
324
325 private Logger resolveLogger( Logger logger, String command )
326 {
327 String partition = System.getProperty( "dpml.station.partition", null );
328 if( null != partition )
329 {
330 return new LoggingAdapter( partition );
331 }
332 else
333 {
334 return logger.getChildLogger( command );
335 }
336 }
337
338 private TransitModel getTransitModel( String[] args )
339 {
340 final String key = "dpml.transit.model";
341 String property = null;
342 for( int i=0; i<args.length; i++ )
343 {
344 String arg = args[i];
345 if( arg.startsWith( "-D" + key + "=" ) )
346 {
347 property = arg.substring( 21 );
348 break;
349 }
350 }
351
352 if( null != property )
353 {
354 if( property.startsWith( "registry:" ) )
355 {
356 try
357 {
358 return (TransitModel) new URL( property ).getContent(
359 new Class[]{TransitModel.class} );
360 }
361 catch( Exception e )
362 {
363 final String error =
364 "Unable to resolve registry reference: " + property;
365 throw new TransitError( error, e );
366 }
367 }
368 else
369 {
370 final String error =
371 "System property value for the key ': "
372 + key
373 + "' contains an unrecognized value: "
374 + property;
375 throw new TransitError( error );
376 }
377 }
378
379 //
380 // otherwise let Transit handle model creation
381 //
382
383 try
384 {
385 Logger logger = getLogger().getChildLogger( "transit" );
386 return DefaultTransitModel.getDefaultModel( logger );
387 }
388 catch( Exception e )
389 {
390 final String error =
391 "Transit model establishment failure.";
392 throw new TransitError( error, e );
393 }
394 }
395
396 private static Logger getLogger()
397 {
398 if( null == m_LOGGER )
399 {
400 String category = System.getProperty( "dpml.logging.category", "depot" );
401 m_LOGGER = new LoggingAdapter( java.util.logging.Logger.getLogger( category ) );
402 }
403 return m_LOGGER;
404 }
405
406 private String toString( String[] args )
407 {
408 StringBuffer buffer = new StringBuffer();
409 for( int i=0; i<args.length; i++ )
410 {
411 if( i > 0 )
412 {
413 buffer.append( ", " );
414 }
415 buffer.append( args[i] );
416 }
417 return buffer.toString();
418 }
419
420 private String[] processSystemProperties( String[] args )
421 {
422 ArrayList result = new ArrayList();
423 for( int i=0; i < args.length; i++ )
424 {
425 String arg = args[i];
426 int index = arg.indexOf( "=" );
427 if( index > -1 && arg.startsWith( "-D" ) )
428 {
429 String name = arg.substring( 2, index );
430 String raw = arg.substring( index + 1 );
431 String value = PropertyResolver.resolve( raw );
432 System.setProperty( name, value );
433 }
434 else
435 {
436 result.add( arg );
437 }
438 }
439 return (String[]) result.toArray( new String[0] );
440 }
441
442 //--------------------------------------------------------------------------
443 // static utilities for setup of logging manager and root prefs
444 //--------------------------------------------------------------------------
445
446 /**
447 * Setup the monitors.
448 */
449 private static void setupMonitors( Transit instance, Adapter adapter ) throws Exception
450 {
451 instance.getRepositoryMonitorRouter().addMonitor(
452 new RepositoryMonitorAdapter( adapter ) );
453 instance.getCacheMonitorRouter().addMonitor(
454 new CacheMonitorAdapter( adapter ) );
455 instance.getNetworkMonitorRouter().addMonitor(
456 new NetworkMonitorAdapter( adapter ) );
457 }
458
459 /**
460 * Create a shutdown hook that will trigger shutdown of the supplied plugin.
461 * @param thread the application thread
462 */
463 public static void setShutdownHook( final Thread thread )
464 {
465 //
466 // Create a shutdown hook to trigger clean disposal of the
467 // controller
468 //
469
470 Runtime.getRuntime().addShutdownHook(
471 new Thread()
472 {
473 public void run()
474 {
475 try
476 {
477 thread.interrupt();
478 }
479 catch( Throwable e )
480 {
481 boolean ignorable = true;
482 }
483 System.runFinalization();
484 }
485 }
486 );
487 }
488
489 /**
490 * DPML build key.
491 */
492 private static final String BUILD_KEY = "dpml.build";
493
494 /**
495 * The Depot system version.
496 */
497 private static final String BUILD_ID = "1.0.2";
498
499 static
500 {
501 setSystemProperty( "java.protocol.handler.pkgs", "net.dpml.transit" );
502 setSystemProperty( "java.util.logging.config.class", "net.dpml.util.ConfigurationHandler" );
503 setSystemProperty( "java.rmi.server.RMIClassLoaderSpi", "net.dpml.depot.DepotRMIClassLoaderSpi" );
504 setSystemProperty( Transit.SYSTEM_KEY, Transit.DPML_SYSTEM.getAbsolutePath() );
505 setSystemProperty( Transit.HOME_KEY, Transit.DPML_HOME.getAbsolutePath() );
506 setSystemProperty( Transit.DATA_KEY, Transit.DPML_DATA.getAbsolutePath() );
507 setSystemProperty( Transit.PREFS_KEY, Transit.DPML_PREFS.getAbsolutePath() );
508 setSystemProperty( BUILD_KEY, BUILD_ID );
509 }
510
511 private static void setSystemProperty( String key, String value )
512 {
513 if( null == System.getProperty( key ) )
514 {
515 System.setProperty( key, value );
516 }
517 }
518
519 private static Logger m_LOGGER = null;
520
521 private Command getCommand( String[] args )
522 {
523 String ref = getApplicationReference( args );
524 String app = System.getProperty( APPLICATION_KEY, ref );
525 return Command.parse( app );
526 }
527
528 private String getApplicationReference( String[] args )
529 {
530 String key = "-D" + APPLICATION_KEY + "=";
531 for( int i=0; i<args.length; i++ )
532 {
533 String arg = args[i];
534 if( arg.startsWith( key ) )
535 {
536 return arg.substring( 25 );
537 }
538 }
539 return null;
540 }
541
542 /**
543 * Application selection key.
544 */
545 public static final String APPLICATION_KEY = "dpml.depot.application";
546
547 /**
548 * Application identifier enumeration.
549 */
550 private static final class Command extends Enum
551 {
552 static final long serialVersionUID = 1L;
553
554 /**
555 * Transit command id.
556 */
557 public static final Command TRANSIT = new Command( "dpml.transit" );
558
559 /**
560 * Metro command id.
561 */
562 public static final Command METRO = new Command( "dpml.metro" );
563
564 /**
565 * Station command id.
566 */
567 public static final Command STATION = new Command( "dpml.station" );
568
569 /**
570 * Builder command id.
571 */
572 public static final Command BUILD = new Command( "dpml.builder" );
573
574 /**
575 * Internal constructor.
576 * @param label the enumeration label.
577 */
578 private Command( String label )
579 {
580 super( label );
581 }
582
583 /**
584 * Create a now mode using a supplied mode name.
585 * @param value the mode name
586 * @return the mode
587 * @exception NullPointerException if the supplied value is null
588 * @exception IllegalArgumentException if the supplied value is not recognized
589 */
590 public static Command parse( String value ) throws NullPointerException, IllegalArgumentException
591 {
592 if( null == value )
593 {
594 final String error =
595 "Undefined sub-system identifier."
596 + "\nThe depot cli handler must be supplied with an -D"
597 + APPLICATION_KEY + "=[id] where id is one of the value 'dpml.metro', "
598 + "'dpml.transit', 'dpml.station' or 'dpml.build'.";
599 throw new NullPointerException( error );
600 }
601 if( value.equalsIgnoreCase( "dpml.metro" ) )
602 {
603 return METRO;
604 }
605 else if( value.equalsIgnoreCase( "dpml.transit" ) )
606 {
607 return TRANSIT;
608 }
609 else if( value.equalsIgnoreCase( "dpml.station" ) )
610 {
611 return STATION;
612 }
613 else if( value.equalsIgnoreCase( "dpml.builder" ) )
614 {
615 return BUILD;
616 }
617 else
618 {
619 final String error =
620 "Unrecognized application id [" + value + "]";
621 throw new IllegalArgumentException( error );
622 }
623 }
624 }
625 }
626